# -*- mode: makefile -*-
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#

#
# Copyright (c) 2014, Joyent, Inc.
#

#
# Makefile.targ: common targets.
#
# NOTE: This makefile comes from the "eng" repo. It's designed to be dropped
# into other repos as-is without requiring any modifications. If you find
# yourself changing this file, you should instead update the original copy in
# eng.git and then update your repo to use the new version.
#
# This Makefile defines several useful targets and rules. You can use it by
# including it from a Makefile that specifies some of the variables below.
#
# Targets defined in this Makefile:
#
#	check	Checks JavaScript files for lint and style
#		Checks bash scripts for syntax
#		Checks SMF manifests for validity against the SMF DTD
#
#	clean	Removes built files
#
#	docs	Builds restdown documentation in docs/
#
#	prepush	Depends on "check" and "test"
#
#	test	Does nothing (you should override this)
#
#	xref	Generates cscope (source cross-reference index)
#
# For details on what these targets are supposed to do, see the Joyent
# Engineering Guide.
#
# To make use of these targets, you'll need to set some of these variables. Any
# variables left unset will simply not be used.
#
#	BASH_FILES	Bash scripts to check for syntax
#			(paths relative to top-level Makefile)
#
#	CLEAN_FILES	Files to remove as part of the "clean" target.  Note
#			that files generated by targets in this Makefile are
#			automatically included in CLEAN_FILES.  These include
#			restdown-generated HTML and JSON files.
#
#	DOC_FILES	Restdown (documentation source) files. These are
#			assumed to be contained in "docs/", and must NOT
#			contain the "docs/" prefix.
#
#	JSL_CONF_NODE	Specify JavaScriptLint configuration files
#	JSL_CONF_WEB	(paths relative to top-level Makefile)
#
#			Node.js and Web configuration files are separate
#			because you'll usually want different global variable
#			configurations.  If no file is specified, none is given
#			to jsl, which causes it to use a default configuration,
#			which probably isn't what you want.
#
#	JSL_FILES_NODE	JavaScript files to check with Node config file.
#	JSL_FILES_WEB	JavaScript files to check with Web config file.
#
#	JSON_FILES	JSON files to be validated
#
#	JSSTYLE_FILES	JavaScript files to be style-checked
#
# You can also override these variables:
#
#	BASH		Path to bash (default: "bash")
#
#	CSCOPE_DIRS	Directories to search for source files for the cscope
#			index. (default: ".")
#
#	JSL		Path to JavaScriptLint (default: "jsl")
#
#	JSL_FLAGS_NODE	Additional flags to pass through to JSL
#	JSL_FLAGS_WEB
#	JSL_FLAGS
#
#	JSON		Path to json tool (default: "json")
#
#	JSSTYLE		Path to jsstyle (default: "jsstyle")
#
#	JSSTYLE_FLAGS	Additional flags to pass through to jsstyle
#
#	RESTDOWN_EXT	By default '.restdown' is required for DOC_FILES
#			(see above). If you want to use, say, '.md' instead, then
#			set 'RESTDOWN_EXT=.md' in your Makefile.
#

#
# Defaults for the various tools we use.
#
BASH		?= bash
BASHSTYLE	?= tools/bashstyle
CP		?= cp
CSCOPE		?= cscope
CSCOPE_DIRS	?= .
JSL		?= jsl
JSON		?= json
JSSTYLE		?= jsstyle
MKDIR		?= mkdir -p
MV		?= mv
RESTDOWN_FLAGS	?=
RESTDOWN_EXT	?= .restdown
RMTREE		?= rm -rf
JSL_FLAGS  	?= --nologo --nosummary

ifeq ($(shell uname -s),SunOS)
	TAR	?= gtar
else
	TAR	?= tar
endif


#
# Defaults for other fixed values.
#
BUILD		= build
DISTCLEAN_FILES += $(BUILD)
DOC_BUILD	= $(BUILD)/docs/public

#
# Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}.
#
ifneq ($(origin JSL_CONF_NODE), undefined)
	JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE)
endif

ifneq ($(origin JSL_CONF_WEB), undefined)
	JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB)
endif

#
# Targets. For descriptions on what these are supposed to do, see the
# Joyent Engineering Guide.
#

#
# Instruct make to keep around temporary files. We have rules below that
# automatically update git submodules as needed, but they employ a deps/*/.git
# temporary file. Without this directive, make tries to remove these .git
# directories after the build has completed.
#
.SECONDARY: $($(wildcard deps/*):%=%/.git)

#
# This rule enables other rules that use files from a git submodule to have
# those files depend on deps/module/.git and have "make" automatically check
# out the submodule as needed.
#
deps/%/.git:
	git submodule update --init deps/$*

#
# These recipes make heavy use of dynamically-created phony targets. The parent
# Makefile defines a list of input files like BASH_FILES. We then say that each
# of these files depends on a fake target called filename.bashchk, and then we
# define a pattern rule for those targets that runs bash in check-syntax-only
# mode. This mechanism has the nice properties that if you specify zero files,
# the rule becomes a noop (unlike a single rule to check all bash files, which
# would invoke bash with zero files), and you can check individual files from
# the command line with "make filename.bashchk".
#
.PHONY: check-bash
check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle)

%.bashchk: %
	$(BASH) -n $^

%.bashstyle: %
	$(BASHSTYLE) $^

.PHONY: check-json
check-json: $(JSON_FILES:%=%.jsonchk)

%.jsonchk: %
	$(JSON) --validate -f $^

#
# The above approach can be slow when there are many files to check because it
# requires that "make" invoke the check tool once for each file, rather than
# passing in several files at once.  For the JavaScript check targets, we define
# a variable for the target itself *only if* the list of input files is
# non-empty.  This avoids invoking the tool if there are no files to check.
#
JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node)
.PHONY: check-jsl-node
check-jsl-node: $(JSL_EXEC)
	$(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE)

JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web)
.PHONY: check-jsl-web
check-jsl-web: $(JSL_EXEC)
	$(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB)

.PHONY: check-jsl
check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET)

JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle)
.PHONY: check-jsstyle
check-jsstyle:  $(JSSTYLE_EXEC)
	$(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES)

.PHONY: check
check: check-jsl check-json $(JSSTYLE_TARGET) check-bash
	@echo check ok

.PHONY: clean
clean::
	-$(RMTREE) $(CLEAN_FILES)

.PHONY: distclean
distclean:: clean
	-$(RMTREE) $(DISTCLEAN_FILES)

CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out
CLEAN_FILES += $(CSCOPE_FILES)

.PHONY: xref
xref: cscope.files
	$(CSCOPE) -bqR

.PHONY: cscope.files
cscope.files:
	find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \
	    -o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@

#
# The "docs" target is complicated because we do several things here:
#
#    (1) Use restdown to build HTML and JSON files from each of DOC_FILES.
#
#    (2) Copy these files into $(DOC_BUILD) (build/docs/public), which
#        functions as a complete copy of the documentation that could be
#        mirrored or served over HTTP.
#
#    (3) Then copy any directories and media from docs/media into
#        $(DOC_BUILD)/media. This allows projects to include their own media,
#        including files that will override same-named files provided by
#        restdown.
#
# Step (3) is the surprisingly complex part: in order to do this, we need to
# identify the subdirectories in docs/media, recreate them in
# $(DOC_BUILD)/media, then do the same with the files.
#
DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$")
DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%)
DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%)

DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null)
DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%)
DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%)

#
# Like the other targets, "docs" just depends on the final files we want to
# create in $(DOC_BUILD), leveraging other targets and recipes to define how
# to get there.
#
.PHONY: docs
docs:							\
	$(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html)		\
	$(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json)		\
	$(DOC_MEDIA_FILES_BUILD)

#
# We keep the intermediate files so that the next build can see whether the
# files in DOC_BUILD are up to date.
#
.PRECIOUS:					\
	$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html)		\
	$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json)

#
# We do clean those intermediate files, as well as all of DOC_BUILD.
#
CLEAN_FILES +=					\
	$(DOC_BUILD)				\
	$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html)		\
	$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json)

#
# Before installing the files, we must make sure the directories exist. The |
# syntax tells make that the dependency need only exist, not be up to date.
# Otherwise, it might try to rebuild spuriously because the directory itself
# appears out of date.
#
$(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD)

$(DOC_BUILD)/%: docs/% | $(DOC_BUILD)
	$(CP) $< $@

docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC)
	$(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $<

$(DOC_BUILD):
	$(MKDIR) $@

$(DOC_MEDIA_DIRS_BUILD):
	$(MKDIR) $@

#
# The default "test" target does nothing. This should usually be overridden by
# the parent Makefile. It's included here so we can define "prepush" without
# requiring the repo to define "test".
#
.PHONY: test
test:

.PHONY: prepush
prepush: check test
